package com.example.diancai.websocket.logic;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.example.diancai.ai.game2048.*;
import com.example.diancai.dto.Vec3DTO;
import com.example.diancai.dto.Vec3StatusDTO;
import com.example.diancai.enums.Game2048LevelEnum;
import com.example.diancai.service.GameUserService;
import com.example.diancai.websocket.dto.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Consumer;

@Service
@Slf4j
public class AiLogic {

    ConcurrentHashMap<String, AiInfo> map = new ConcurrentHashMap<>();
    private final ExecutorService executorService = Executors.newFixedThreadPool(20);

    @Autowired
    @Lazy
    private GameLogic gameLogic;

    public void accept(ClientInfoDTO clientInfoDTO, SocketMessage socketMessage) {
        String event = socketMessage.getEvent();
        if (event.equals("home/matchSuccess")) {
            this.matchSuccess(clientInfoDTO, socketMessage);
            return;
        } else if (event.equals("manyGame2048/opponentUseProp")) {
            // 对手使用道具
            this.opponentUseProp(clientInfoDTO, socketMessage);
        } else if (event.equals("manyGame2048/StartGame")) {
            // 对手使用道具
            this.matchSuccess(clientInfoDTO, socketMessage);
        } else {
            log.info("ai消息未处理:{}", socketMessage.getEvent());
        }
    }


    private void opponentUseProp(ClientInfoDTO clientInfoDTO, SocketMessage socketMessage) {
        // 对方使用道具,放进池子里
        UsePropDTO usePropDTO = (UsePropDTO) socketMessage.getData();
        Grid grid = map.get(clientInfoDTO.getRoofNo()).grid;
        grid.usePropDTOS.add(usePropDTO);
        SocketMessage socketMessage2 = new SocketMessage();
        socketMessage2.setEvent("usePropSuccess");
        socketMessage2.setData(usePropDTO);
        gameLogic.usePropSuccess(clientInfoDTO, JSON.toJSONString(socketMessage2));
    }

    private void matchSuccess(ClientInfoDTO clientInfoDTO, SocketMessage socketMessage) {
        // 初始化ai
        Grid grid = new Grid(Integer.parseInt(clientInfoDTO.getLv()));
        List<List<Vec3DTO>> before = clientInfoDTO.getBefore();
        calcArray(before, vec3DTO -> {
            if (vec3DTO.getZ() != 0) {
                grid.insertTile(new Tile(vec3DTO));
            }
        });
        RoofInfoDTO roofInfoDTO = (RoofInfoDTO) socketMessage.getData();
        List<ClientInfoDTO> opponent = gameLogic.getOpponent(roofInfoDTO.getPlayers(), clientInfoDTO);
        int level = getLevel(opponent.get(0).getCurrentPlayer().getGame2048Level());
        log.info("ai等级:{}", level);
        grid.setLevel(level);
        AiInfo aiInfo = new AiInfo();
        aiInfo.clientInfoDTO = clientInfoDTO;
        aiInfo.grid = grid;
        map.put(clientInfoDTO.getRoofNo(), aiInfo);
    }


    /**
     * 根据对方等级,匹配ai等级
     *
     * @param levelEnum
     * @return
     */
    private int getLevel(Game2048LevelEnum levelEnum) {
        if (levelEnum.ordinal() < 3) {
            return 1;
        }
        if (levelEnum.ordinal() < 7) {
            return 2;
        }
        if (levelEnum.ordinal() < 15) {
            return 3;
        }
        if (levelEnum.ordinal() < 20) {
            return 4;
        }
        if (levelEnum.ordinal() < 25) {
            return 5;
        }
        return 6;
    }

    private void calcArray(List<List<Vec3DTO>> array, Consumer<Vec3DTO> consumer) {
        for (List<Vec3DTO> list : array) {
            for (Vec3DTO vec3DTO : list) {
                consumer.accept(vec3DTO);
            }
        }
    }

    public void move() {
        if (CollUtil.isNotEmpty(map)) {
            for (String roofNo : map.keySet()) {
                AiInfo aiInfo = map.get(roofNo);
                RoofInfoDTO roofInfoDTO = gameLogic.getRoomInfoByClient(aiInfo.getClientInfoDTO());
                // 游戏已经开始,且没有结束
                if (roofInfoDTO != null
                        && roofInfoDTO.getStartDateTime().isBefore(LocalDateTime.now())
                        && roofInfoDTO.getStatus() == 1) {
                    // 不在ai线程池,才执行
                    if (!aiInfo.getGrid().isSearch) {
                        log.info("startzhixing");
                        aiStart(aiInfo.clientInfoDTO, aiInfo.grid);
                    }
                } else if (roofInfoDTO == null || roofInfoDTO.getStatus() == 2) {
                    log.info("移除ai");
                    map.remove(roofNo);
                }
            }
        }
    }

    public void aiStart(ClientInfoDTO clientInfoDTO, Grid grid) {
        log.info("执行grid {}", JSON.toJSONString(grid));
        grid.isSearch = true;
        executorService.submit(() -> {
            try {
                if (StrUtil.isEmpty(clientInfoDTO.getRoofNo())) {
                    log.info("游戏结束,不执行ai");
                    this.prepare(clientInfoDTO);
                    return;
                }
                if (!map.containsKey(clientInfoDTO.getRoofNo())) {
                    log.info("游戏结束,不执行ai");
                    this.prepare(clientInfoDTO);
                    return;
                }


                log.info("执行grid开始 {}", JSON.toJSONString(grid));

                //如果有对方使用的道具,优先道具造成损伤
                if (CollUtil.isNotEmpty(grid.getUsePropDTOS())) {
                    UsePropDTO usePropDTO = grid.usePropDTOS.pop();
                    switch (usePropDTO.getProp().getType()) {
                        case 50:
                            // 炸弹
                            this.oppentBoom(clientInfoDTO, usePropDTO, grid);
                            break;
                        case 51:
                            // 冻结
                            this.opponentUseFrozen(clientInfoDTO, usePropDTO, grid);
                            break;
                        case 100:
                            // 翻转
                            this.opponentUseReversed(clientInfoDTO, usePropDTO, grid);
                            break;
                        case 101:
                            // 翻转
                            this.opponentUseCurse(clientInfoDTO, usePropDTO, grid);
                            break;
                        case 151:
                            // 策反
                            this.opponentUseFormulate(clientInfoDTO, usePropDTO, grid);
                            break;
                        default: {

                        }
                        break;
                    }
                }

                //TODO 我方使用道具

                //
                log.info("执行前");
                printCells(grid);
                Ai2048 ai2048 = new Ai2048(grid);


                // 不能合并,并且空格数只剩下一个,不滑动了,等待游戏结束


                SearchResult best = ai2048.getBest();
                log.info("ai结果:{}", JSON.toJSONString(best));

                if (best == null) {
                    waitGameOver(clientInfoDTO, grid);
                    return;
                }
                if (
                        best.move == -1
                                || best.cutoffs != 0) {
                    log.info("游戏结束");
                    // 游戏结束
                    SocketMessage socketMessage = new SocketMessage();
                    socketMessage.setEvent("failure");
                    gameLogic.failure(clientInfoDTO, JSON.toJSONString(socketMessage));

                    this.prepare(clientInfoDTO);
                    return;
                }
                Integer move = best.move;
                // ai进行移动
                Grid testGrid = grid.clone();

                testGrid.move(move);
                // boolean notMerge = isNotMerge(grid.getCells());

                // 不能合并,并且空格数只剩下一个,不滑动了,等待游戏结束
                log.info("测试cell:{}", JSON.toJSONString(testGrid.getCells()));
                printCells(testGrid);

                int i = tileEmpNum(testGrid.getCells());
                log.info("空格数:{}", i);
                if (i == 1) {
                    boolean notMerge = isNotMerge(testGrid.getCells());
                    if (notMerge) {
                        waitGameOver(clientInfoDTO, grid);
                        return;
                    }
                }

                log.info("滑动grid本体前");
                printCells(grid);

                MoveResult moveResult = grid.move(move);
                clientInfoDTO.setScore(clientInfoDTO.getScore() + moveResult.getScore());


                log.info("执行滑动后");
                printCells(grid);

                // 生成随机数
                Tile tile = randomArray(grid.getCells());
                if (tile == null) {
                    // 游戏结束
                    log.info("游戏结束");
                    SocketMessage socketMessage = new SocketMessage();
                    socketMessage.setEvent("failure");
                    gameLogic.failure(clientInfoDTO, JSON.toJSONString(socketMessage));

                    this.prepare(clientInfoDTO);
                    return;
                }
                grid.insertTile(tile);
                grid.playerTurn = true;

                List<List<Vec3DTO>> lists = transTile(grid.getCells());
                if (CollUtil.isNotEmpty(clientInfoDTO.getCurrent())) {
                    clientInfoDTO.setBefore(clientInfoDTO.getCurrent());
                }
                clientInfoDTO.setCurrent(lists);

                // 通知对方

                if (StrUtil.isEmpty(clientInfoDTO.getRoofNo())) {
                    log.info("游戏结束,不执行ai");
                    this.prepare(clientInfoDTO);
                    return;
                }
                if (!map.containsKey(clientInfoDTO.getRoofNo())) {
                    log.info("游戏结束,不执行ai");
                    this.prepare(clientInfoDTO);
                    return;
                }

                RoofInfoDTO roofInfoDTO = gameLogic.getRoomInfoByClient(clientInfoDTO);

                SocketMessage socketMessage = new SocketMessage();
                socketMessage.setEvent("manyGame2048/rivalMove");
                socketMessage.setData(clientInfoDTO);
                // 通知对方移动了
                gameLogic.sendMessage(gameLogic.getOpponent(roofInfoDTO.getPlayers(), clientInfoDTO).get(0), socketMessage);
                //
                //  Thread.sleep(grid.getMinSearchTime());
                this.aiStart(clientInfoDTO, grid);
                log.info("执行grid结束 {}", JSON.toJSONString(grid));

            } catch (Exception e) {
                log.error("ai异常", e);
            }
        });
    }

    private void waitGameOver(ClientInfoDTO clientInfoDTO, Grid grid) throws InterruptedException {
        // 响应道具信息
        // 通知对方
        RoofInfoDTO roofInfoDTO = gameLogic.getRoomInfoByClient(clientInfoDTO);
        SocketMessage socketMessage = new SocketMessage();
        socketMessage.setEvent("manyGame2048/rivalMove");
        socketMessage.setData(clientInfoDTO);
        // 通知对方移动了
        gameLogic.sendMessage(gameLogic.getOpponent(roofInfoDTO.getPlayers(), clientInfoDTO).get(0), socketMessage);


        // 万一被炸弹炸掉了,就能继续滑动了
        Thread.sleep(500);
        this.aiStart(clientInfoDTO, grid);
        log.info("等待游戏结束");
    }

    private void printCells(Grid grid) {
        for (int i = 0; i < 4; i++) {
            if (grid.getCells()[i] == null) {
                System.out.print("0");
                continue;
            }
            for (int j = 0; j < 4; j++) {
                if (grid.getCells()[i][j] == null) {
                    System.out.print("0");
                    continue;
                }

                System.out.print(grid.getCells()[i][j].value);
            }
            System.out.println("");
        }
    }

    private void opponentUseFormulate(ClientInfoDTO clientInfoDTO, UsePropDTO usePropDTO, Grid grid) {
        SocketMessage socketMessage = new SocketMessage();
        socketMessage.setEvent("usePropSuccess");
        Tile tile = randomSquare(1, grid.getCells());
        if (tile != null) {

            grid.getCells()[tile.getX()][tile.getY()].status.add(new Vec3StatusDTO(usePropDTO.getProp().getType(), 999));
            usePropDTO.setSelectEnemyPosition(List.of(new Vec3DTO(grid.getCells()[tile.getX()][tile.getY()].x,
                    grid.getCells()[tile.getX()][tile.getY()].y,
                    grid.getCells()[tile.getX()][tile.getY()].value)));
        }
        socketMessage.setData(usePropDTO);
        //   gameLogic.usePropSuccess(clientInfoDTO, JSON.toJSONString(socketMessage));
    }

    private void opponentUseCurse(ClientInfoDTO clientInfoDTO, UsePropDTO usePropDTO, Grid grid) {
        SocketMessage socketMessage = new SocketMessage();
        socketMessage.setEvent("usePropSuccess");
        Tile tile = randomSquare(1, grid.getCells());
        if (tile != null) {

            grid.getCells()[tile.getX()][tile.getY()].status.add(new Vec3StatusDTO(usePropDTO.getProp().getType(), 999));
            usePropDTO.setSelectEnemyPosition(List.of(new Vec3DTO(grid.getCells()[tile.getX()][tile.getY()].x,
                    grid.getCells()[tile.getX()][tile.getY()].y,
                    grid.getCells()[tile.getX()][tile.getY()].value)));
        }
        socketMessage.setData(usePropDTO);
        //    gameLogic.usePropSuccess(clientInfoDTO, JSON.toJSONString(socketMessage));
    }

    private void opponentUseReversed(ClientInfoDTO clientInfoDTO, UsePropDTO usePropDTO, Grid grid) {
        SocketMessage socketMessage = new SocketMessage();
        socketMessage.setEvent("usePropSuccess");
        // 数组翻转
        grid.setCells(rotateMatrixClockwise(grid.getCells()));

        socketMessage.setData(usePropDTO);
        //  gameLogic.usePropSuccess(clientInfoDTO, JSON.toJSONString(socketMessage));

    }

    private Tile[][] rotateMatrixClockwise(Tile[][] matrix) {
        int n = matrix.length;
        int m = matrix[0].length;
        Tile[][] rotated = new Tile[m][n];

        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                rotated[j][n - 1 - i] = matrix[i][j];
                if (rotated[j][n - 1 - i] != null) {
                    rotated[j][n - 1 - i].x = j;
                    rotated[j][n - 1 - i].y = Math.abs(i - 3);
                }
            }
        }
        return rotated;
    }


    private void opponentUseFrozen(ClientInfoDTO clientInfoDTO, UsePropDTO usePropDTO, Grid grid) {
        SocketMessage socketMessage = new SocketMessage();
        socketMessage.setEvent("usePropSuccess");
        Tile tile = randomSquare(1, grid.getCells());
        if (tile != null) {

            grid.getCells()[tile.getX()][tile.getY()].status.add(new Vec3StatusDTO(usePropDTO.getProp().getType(), 2));
            usePropDTO.setSelectEnemyPosition(List.of(new Vec3DTO(grid.getCells()[tile.getX()][tile.getY()].x,
                    grid.getCells()[tile.getX()][tile.getY()].y,
                    grid.getCells()[tile.getX()][tile.getY()].value)));
        }
        socketMessage.setData(usePropDTO);
        // gameLogic.usePropSuccess(clientInfoDTO, JSON.toJSONString(socketMessage));
    }

    private void oppentBoom(ClientInfoDTO clientInfoDTO, UsePropDTO usePropDTO, Grid grid) {
        // 炸弹
        SocketMessage socketMessage = new SocketMessage();
        socketMessage.setEvent("usePropSuccess");

        Tile tile = randomSquare(1, grid.getCells());
        if (tile != null) {
            // 删除数字
            usePropDTO.setSelectEnemyPosition(List.of(new Vec3DTO(grid.getCells()[tile.getX()][tile.getY()].x,
                    grid.getCells()[tile.getX()][tile.getY()].y,
                    grid.getCells()[tile.getX()][tile.getY()].value)));
            //减掉分数
            clientInfoDTO.setScore(clientInfoDTO.getScore() - grid.getCells()[tile.getX()][tile.getY()].value);
            grid.removeTile(grid.getCells()[tile.getX()][tile.getY()]);

        }

        // 通知对方
        socketMessage.setData(usePropDTO);
        // gameLogic.usePropSuccess(clientInfoDTO, JSON.toJSONString(socketMessage));

    }


    /**
     * 随机棋盘一个格子
     *
     * @param type 1:有数字的 2：没有
     * @private
     */
    private Tile randomSquare(int type, Tile[][] tiles) {
        List<Tile> conform = new ArrayList<>();
        calcArray(tiles, tile -> {
            if ((tile == null || tile.value == 0) && type == 2) {
                conform.add(tile);
            } else if (tile != null && tile.value != 0 && type == 1) {
                conform.add(tile);
            }
        });
        if (CollUtil.isEmpty(conform)) {
            return null;
        }
        return RandomUtil.randomEle(conform);
    }


    private List<List<Vec3DTO>> transTile(Tile[][] tiles) {
        List<List<Vec3DTO>> list = new ArrayList<>();
        for (int i = 0; i < tiles.length; i++) {
            List<Vec3DTO> vec3DTOS = new ArrayList<>();
            for (int j = 0; j < tiles[i].length; j++) {
                if (tiles[i][j] != null) {
                    Vec3DTO vec3DTO = new Vec3DTO(
                            tiles[i][j].x,
                            tiles[i][j].y,
                            tiles[i][j].value
                    );
                    vec3DTO.setStatus(tiles[i][j].status);
                    vec3DTOS.add(vec3DTO);
                } else {
                    Vec3DTO vec3DTO = new Vec3DTO(
                            i,
                            j,
                            0
                    );
                    vec3DTO.setStatus(new ArrayList<>());
                    vec3DTOS.add(vec3DTO);
                }

            }
            list.add(vec3DTOS);
        }
        return list;
    }

    private void calcArray(Tile[][] array, Consumer<Tile> consumer) {
        for (Tile[] list : array) {
            for (Tile tile : list) {
                consumer.accept(tile);
            }
        }
    }

    private int tileEmpNum(Tile[][] tiles) {
        int num = 0;
        for (int x = 0; x < 4; x++) {
            for (int y = 0; y < 4; y++) {
                Tile tile = tiles[x][y];
                if (tile == null) {
                    num++;
                }
            }
        }
        return num;
    }

    private Tile randomArray(Tile[][] tiles) {
        // 取出空格
        List<Vec3DTO> vec3DTOS = new ArrayList<>();
        for (int x = 0; x < 4; x++) {
            for (int y = 0; y < 4; y++) {
                Tile tile = tiles[x][y];
                if (tile == null) {
                    vec3DTOS.add(new Vec3DTO(x, y));
                }
            }
        }
        if (CollUtil.isEmpty(vec3DTOS)) {
            return null;
        }

        Vec3DTO vec3DTO = RandomUtil.randomEle(vec3DTOS);

        Integer num = RandomUtil.randomEle(CollUtil.newArrayList(2, 4));

        return new Tile(new Vec3DTO(vec3DTO.x, vec3DTO.y, num));
    }

/*    public static void main(String[] args) {
        AiLogic aiLogic = new AiLogic();
        JSONArray array = JSON.parseArray("[[{\"x\":0.0,\"y\":0.0,\"z\":256.0,\"status\":[]},{\"x\":0.0,\"y\":1.0,\"z\":16.0,\"status\":[]},{\"x\":0.0,\"y\":2.0,\"z\":2.0,\"status\":[]},{\"x\":0.0,\"y\":3.0,\"z\":4.0,\"status\":[]}],[{\"x\":1.0,\"y\":0.0,\"z\":32.0,\"status\":[]},{\"x\":1.0,\"y\":1.0,\"z\":32.0,\"status\":[{\"status\":101,\"count\":999}]},{\"x\":1.0,\"y\":2.0,\"z\":4.0,\"status\":[]},{\"x\":1.0,\"y\":3.0,\"z\":2.0,\"status\":[]}],[{\"x\":2.0,\"y\":0.0,\"z\":32.0,\"status\":[]},{\"x\":2.0,\"y\":1.0,\"z\":4.0,\"status\":[]},{\"x\":2.0,\"y\":2.0,\"z\":2.0,\"status\":[]},{\"x\":2.0,\"y\":3.0,\"z\":4.0,\"status\":[]}],[{\"x\":3.0,\"y\":0.0,\"z\":8.0,\"status\":[]},{\"x\":3.0,\"y\":1.0,\"z\":4.0,\"status\":[]},{\"x\":3.0,\"y\":2.0,\"z\":2.0,\"status\":[]},{\"x\":3.0,\"y\":3.0,\"z\":4.0,\"status\":[]}]]");
        Tile[][] tiles = new Tile[4][4];
        for (int i = 0; i < array.size(); i++) {
            JSONArray o = (JSONArray) array.get(i);
            for (int j = 0; j < o.size(); j++) {
                JSONObject o1 = (JSONObject) o.get(j);
                List<Vec3StatusDTO> status = JSON.parseArray(o1.getString("status"), Vec3StatusDTO.class);

                tiles[i][j] = new Tile(new Vec3DTO(o1.getBigDecimal("x").floatValue(),
                        o1.getBigDecimal("y").floatValue(),
                        o1.getBigDecimal("z").floatValue(),status));
            }
        }


        boolean notMerge = aiLogic.isNotMerge(tiles);
        System.out.println(notMerge);
    }*/

    public boolean isNotMerge(Tile[][] tiles) {
        boolean isGameOver = true;
        outerLoop:
        for (int i = 0; i < tiles.length; i++) {
            int columnLength = tiles[i].length;
            for (int j = 0; j < columnLength; j++) {
                Tile currentTile = tiles[i][j];
                if (currentTile != null && Grid.isContains(currentTile.getStatus(), Arrays.asList(51, 101))) {
                    continue;
                }
                if (i + 1 < tiles.length && currentTile != null && tiles[i + 1][j] != null
                        && currentTile.getValue() == tiles[i + 1][j].getValue()
                        && !Grid.isContains(tiles[i + 1][j].getStatus(), Arrays.asList(51, 101))) {
                    isGameOver = false;
                    break outerLoop;
                }
                if (i - 1 >= 0 && currentTile != null && tiles[i - 1][j] != null
                        && currentTile.getValue() == tiles[i - 1][j].getValue()
                        && !Grid.isContains(tiles[i - 1][j].getStatus(), Arrays.asList(51, 101))) {
                    isGameOver = false;
                    break outerLoop;
                }
                if (j + 1 < columnLength && currentTile != null && tiles[i][j + 1] != null
                        && currentTile.getValue() == tiles[i][j + 1].getValue()
                        && !Grid.isContains(tiles[i][j + 1].getStatus(), Arrays.asList(51, 101))) {
                    isGameOver = false;
                    break outerLoop;
                }
                if (j - 1 >= 0 && currentTile != null && tiles[i][j - 1] != null
                        && currentTile.getValue() == tiles[i][j - 1].getValue()
                        && !Grid.isContains(tiles[i][j - 1].getStatus(), Arrays.asList(51, 101))) {
                    isGameOver = false;
                    break outerLoop;
                }
            }
        }
        return isGameOver;
    }


    public void removeAI(String roofNo) {
        map.remove(roofNo);
    }

    public void prepare(ClientInfoDTO clientInfoDTO) {
        log.info("ai已准备");
        SocketMessage<Integer> socketMessage = new SocketMessage<>();
        socketMessage.setData(1);
        socketMessage.setMessage("manyGame2048");
        gameLogic.updatePlayerStatus(clientInfoDTO, JSON.toJSONString(socketMessage));
    }
}
